//core component of this project, to implement a single cycle one

//import the sub modules
`include "alu.v"
`include "reg_file.v"
`include "data_mem.v"
`include "instr_mem.v"


module Controller (reset,clk);
  parameter word_size=32;
  parameter reg_addr_size=5;
  parameter mem_addr_size=32;
  parameter op_size=6;
  parameter func_size=6;
  parameter data_mem_size=32'h0000_0040;
  parameter instr_mem_size=32'h0000_0010;
  parameter j_addr_size=26;
  parameter imm_size=16;
  parameter instr_stride=1; // 1-word stride, for next instr

  parameter rs_hi=word_size-op_size;
  parameter rt_hi=rs_hi-reg_addr_size;
  parameter rd_hi=rt_hi-reg_addr_size;
  parameter sa_hi=rd_hi-reg_addr_size;
  parameter func_hi=sa_hi-reg_addr_size;



  //defined instructions
  parameter JUMP=6'b000010;
  parameter SPECIAL=6'b0; // NOP is also included in it
  parameter ADDI=6'b001000;
  parameter ANDI=6'b001100;
  parameter ORI=6'b001101;
  parameter SLTI=6'b001010;
  parameter SW=6'b101011;
  parameter LW=6'b100011;

  parameter SHIFT=6'b0; //the func code for shift

  parameter TRUE=1'b1;
  parameter FALSE=1'b0;

  parameter ADD=6'b100000;
  parameter AND=6'b100100;
  parameter OR=6'b100101;
  parameter SLT=6'b101010;




  input clk;
  input reset; //for loading instructions into a blank memory and init register file


  //internal state,
  reg [mem_addr_size-1:0] pc; //specific register PC, init as ptr to header
  reg z=FALSE; //zero-flag
  reg aluimm=FALSE; //disable it when I-Format
  reg regrt=FALSE; //enable it when I-Format
  reg wreg=FALSE;
  reg wmem=FALSE;
  reg jump=FALSE;
  reg m2reg=FALSE;
  reg branch=FALSE;
  reg [func_size-1:0] aluc=FALSE;
  reg shift=FALSE;
  reg sext=FALSE;


  reg mem_we=FALSE;
  reg reg_we=FALSE;

  //register monitor
  initial begin
    //$monitor("%g\tPC:%x\tALUIMM:%x\tREGRT:%x\tWREG:%x\tWMEM:%x\tJUMP:%x\tM2REG:%x\tALUC:%x\tSHIFT:%x\tSEXT:%x\t",$time,pc,aluimm,regrt,wreg,wmem,jump,m2reg,aluc,shift,sext);
  end

  //for fetch phase
  wire [word_size-1:0] instr;
  wire [op_size-1:0] op;
  wire [reg_addr_size-1:0] rs, rt, rd, sa;
  wire [imm_size-1:0] imm;
  wire [j_addr_size-1:0] j_addr;
  wire [func_size-1:0] func;





  //for register
  reg [reg_addr_size-1:0] addr_a=5'b0;
  reg [reg_addr_size-1:0] addr_b=5'b0;
  reg [reg_addr_size-1:0] addr_w=5'b0;


  //for alu unit & Exec
  wire [word_size-1:0] addr_a_out_val;
  wire [word_size-1:0] addr_b_out_val;


  wire [word_size-1:0] signed_imm;

  reg [word_size-1:0] operand_a, operand_b;
  wire w_z; //z flag from alu
  wire [word_size-1:0] outcome;



  //for mem io and write back
  reg [mem_addr_size-1:0] mem_addr;
  reg [word_size-1:0] mem_in_val;
  wire [word_size-1:0] mem_out_val;

  reg [word_size-1:0] reg_in_val;




  //construct submodules

  //module SignExtender(imm, signed_imm, sext);
  SignExtender sign_extender(imm, signed_imm, sext);
  //module Demux (instr, op, rs, rt, rd, sa, func, imm, j_addr)
  Demux demux(instr, op, rs, rt, rd, sa, func, imm, j_addr);
  //module ALU (operand_a, operand_b, op, outcome, z);
  ALU alu(operand_a, operand_b, aluc, outcome, w_z);
  //module DataMem (addr_a, in_val, we, reset, out_a);
  DataMem data_mem(mem_addr, mem_in_val, mem_we, reset, mem_out_val);
  //module InstrMem (addr_a, out_a, reset);
  InstrMem instr_mem(pc,instr,reset);
  //module RegFile (addr_a, addr_b, addr_w, in_val, we, reset, out_a, out_b, clk); posedge read, negedge write
  RegFile reg_file(addr_a, addr_b, addr_w, reg_in_val, reg_we, reset, addr_a_out_val, addr_b_out_val, clk);

  always @ (reset) begin
    if(reset==1'b1) begin
      pc<=32'b0;
    end
  end



  // -> Exec -> memIO & writeBack
  always @ (posedge clk) begin
    //prepare write address for register
    addr_w<=(regrt)? rt:rd;
    #0.5 ; //wait register read
    $display("r%d -> 0x%x", addr_a, addr_a_out_val);
    $display("r%d -> 0x%x", addr_b, addr_b_out_val);


    //activate the alu data
    operand_a<=(shift==TRUE)? sa:addr_a_out_val;
    operand_b<=(aluimm==TRUE)? signed_imm:addr_b_out_val;


    //wait for alu finish
    #0.5
    $display("ALU Info: A:0x%x, B:0x%x, op:0x%x, O:%x",operand_a, operand_b, aluc,outcome);
    z<=w_z; //update z flag
    mem_in_val<=addr_b_out_val;
    mem_addr<=outcome;
    //activate mem write flag
    mem_we<=wmem;


    //check whether write back
    //enable reg we and it will do the write after next posedge, since addr is set
    #0.5 ;
    if(mem_we==TRUE) begin //display for mem write
        if(mem_addr>=data_mem_size) begin
          $display("(Fail W Out of Bound)DMem[0x%x] <- 0x%x", mem_addr, mem_in_val);
        end
        else begin
          $display("DMem[0x%x] <- 0x%x", mem_addr, mem_in_val);
        end
    end
    else begin //display for mem read
        if(mem_addr>=data_mem_size) begin
            $display("(Fail R Out of Bound)DMem[0x%x] -> 0x%x", mem_addr, mem_out_val);
        end
        else begin
            $display("DMem[0x%x] -> 0x%x", mem_addr, mem_out_val);
        end

    end
    mem_we=FALSE;
    reg_in_val=(m2reg==TRUE)? mem_out_val:outcome;
    reg_we=wreg; //activate register write enabler until after the next negedge



    if(wreg==TRUE) begin
      $display("r%d <- 0x%x", addr_w, reg_in_val);
    end
    //update pc
    #0.5;
    pc=(jump)? {2'b00,{pc[word_size-1:word_size-4]},j_addr}:pc+1; //since the address here is in unit word
  end

  //system start at the first negedge
  always @ ( negedge clk ) begin
    $display("\n\nPC:0x%x -> 0x%x", pc, instr);
    //update internal state
    case (op) SPECIAL: begin //for ADD, AND, OR, SUB, SLT, NOP
              aluimm<=FALSE;
              regrt<=FALSE;
              wreg<=TRUE; //only a mark
              wmem<=FALSE;
              jump<=FALSE;
              m2reg<=FALSE; //nothing to do with memory
              //branch<=FALSE;
              shift<=(func==SHIFT)? TRUE:FALSE;
              sext<=FALSE;
              aluc<=func;
          end
          ADDI,ANDI,ORI,SLTI: begin
              aluimm<=TRUE;
              regrt<=TRUE; //use rt as the dst register
              wreg<=TRUE; //only a mark
              wmem<=FALSE;
              jump<=FALSE;
              m2reg<=FALSE;
              //branch<=FALSE;
              shift<=FALSE;
              sext<=TRUE;
              case (op)
                ADDI: aluc<=ADD;
                ANDI: aluc<=AND;
                ORI: aluc<=OR;
                SLTI: aluc<=SLT;
              endcase
          end
          JUMP:begin
              //absolute address, nothing to do with the alu
              aluimm<=FALSE;
              regrt<=FALSE;
              wreg<=FALSE;
              wmem<=FALSE;
              jump<=TRUE;
              m2reg<=FALSE;
              //branch<=TRUE;
              shift<=FALSE;
              sext<=FALSE;
              aluc<=ADD;
          end
          SW,LW:begin
            aluimm<=TRUE; //for offset
            regrt<=TRUE;
            wreg<=(op==LW)? TRUE:FALSE; //if load word, register modified. only a mark
            wmem<=(op==SW)? TRUE:FALSE; //if store word, memory modified. only a mark
            jump<=FALSE;
            m2reg<=TRUE; //write the memory data or just the address, ctrl whether alu outcome pass in the mem module
            //branch<=FALSE;
            shift<=FALSE;
            sext<=TRUE; //signed extend offset
            aluc<=ADD;
          end
          default: $display("OP %x not Defined",op);
      endcase



      //prepare address
      addr_a=rs;
      addr_b=rt;
  end


endmodule //


//a submodule for the main controller, only do the split
module Demux (instr, op, rs, rt, rd, sa, func, imm, j_addr);
  parameter reg_addr_size=5;
  parameter instr_size=32;
  parameter op_size=6;
  parameter func_size=6;
  parameter j_addr_size=26;
  parameter imm_size=16;

  parameter rs_hi=instr_size-op_size;
  parameter rt_hi=rs_hi-reg_addr_size;
  parameter rd_hi=rt_hi-reg_addr_size;
  parameter sa_hi=rd_hi-reg_addr_size;
  parameter func_hi=sa_hi-reg_addr_size;



  input [instr_size-1:0] instr;
  output [op_size-1:0] op;
  output [j_addr_size-1:0] j_addr;
  output [imm_size-1:0] imm;
  output [reg_addr_size-1:0] rs,rd,rt,sa;
  output [func_size-1:0] func;

  reg op,rs,rd,rt,sa,func,imm,j_addr;


  always @ (instr) begin
      //do demux without consider the overlapping
      op<=instr[instr_size-1:instr_size-op_size];
      rs<=instr[rs_hi-1:rt_hi];
      rt<=instr[rt_hi-1:rd_hi];
      rd<=instr[rd_hi-1:sa_hi];
      sa<=instr[sa_hi-1:func_hi];
      func<=instr[func_hi-1:0];
      imm<=instr[imm_size-1:0];
      j_addr<=instr[j_addr_size-1:0];
  end

endmodule // demux

module SignExtender(imm, signed_imm, sext);
  parameter imm_size=16;
  parameter word_size=32;

  input [imm_size-1:0] imm;
  input sext;
  output [word_size-1:0] signed_imm;
  reg signed_imm;
  reg sign;

  always @ (imm or sext) begin
    sign=(sext)? imm[imm_size-1]:1'b0;
    signed_imm={{(word_size-imm_size){sign}},imm};
  end
endmodule

`include "clock.v"

module sc_cpu_sim ();

  wire clk;
  reg reset;

  ClockGen clock_generator(clk);
  Controller sc_cpu(reset,clk);

  initial begin
    $dumpfile("sc_cpu_sim.vcd");
    $dumpvars(0,sc_cpu_sim);
    reset=0;
    //init the state before #10 the first pos edge
    #1 reset=1;
    #1 reset=0;

    #157 $finish;
  end


endmodule // simulation for single cycle cpu
